use std::{error::Error, net::Ipv4Addr};
use clap::Parser;
use reqwest;
use libp2p::{
    tcp, autonat, noise, 
    core::{multiaddr::Protocol, Multiaddr},
    identity, identify,
    swarm::{SwarmEvent, NetworkBehaviour}, yamux,
    futures::StreamExt,
};

use tracing_subscriber::EnvFilter;

#[derive(Debug, Parser)]
#[command(name = "my_autonat_server")]
struct Opt {
    #[arg(long)]
    listen_port: Option<u16>,
}

async fn fetch_public_ip() -> Result<String, reqwest::Error> {
    let response = reqwest::get("https://icanhazip.com").await?;
    let ip = response.text().await?;
    Ok(ip.trim().to_string())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let _ = tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .try_init();
    
    let opt = Opt::parse();
    
    let mut swarm = libp2p::SwarmBuilder::with_new_identity()
    .with_tokio()
    .with_tcp(
        tcp::Config::default(), 
        noise::Config::new, 
        yamux::Config::default,
    )?
    .with_behaviour(
        |key| {Behaviour::new(key.public())})?
    .build();
    println!("Server Peer ID: {}", swarm.local_peer_id());

    // 获取并打印公网IP
    match fetch_public_ip().await {
        Ok(ip) => println!("Public IP Address: {}", ip),
        Err(e) => eprintln!("Failed to fetch public IP: {}", e),
    }

    swarm.listen_on(
        Multiaddr::empty()
            .with(Protocol::Ip4(Ipv4Addr::UNSPECIFIED))
            .with(Protocol::Tcp(opt.listen_port.unwrap_or(0))),
    )?;

    loop {
        match swarm.select_next_some().await {
            SwarmEvent::NewListenAddr { address, .. } => {
                println!("Listening on: {address:?}");
            }
            SwarmEvent::Behaviour(event) => {
                println!("{event:?}");
            }
            e => println!("{e:?}"),
        }
    }
}

#[derive(NetworkBehaviour)]
struct Behaviour {
    identity: identify::Behaviour,
    auto_nat: autonat::Behaviour,
}

impl Behaviour {
    fn new(local_public_key: identity::PublicKey) -> Self {
        Self {
            identity: identify::Behaviour::new(identify::Config::new(
                "/ipfs/0.1.0".into(),
                local_public_key.clone(),
            )),
            auto_nat: autonat::Behaviour::new(
                local_public_key.to_peer_id(),
                autonat::Config {
                    only_global_ips: false,
                    ..Default::default()
                },
            ),
        }
    }
}
